/**
 
 * 
 */
package nl.boektrust.btsoep.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import nl.boektrust.btsoep.shared.model.AvailabilityCode;
import nl.boektrust.btsoep.shared.model.ContentSpecifier;
import nl.boektrust.btsoep.shared.model.ContentSpecifierType;
import nl.boektrust.btsoep.shared.model.Contributor;
import nl.boektrust.btsoep.shared.model.ContributorRoleCode;
import nl.boektrust.btsoep.shared.model.ImageReference;
import nl.boektrust.btsoep.shared.model.ImageReferenceTypeCode;
import nl.boektrust.btsoep.shared.model.PhysicalBookManifestation;
import nl.boektrust.btsoep.shared.model.Price;
import nl.boektrust.btsoep.shared.model.ProductFormCode;
import nl.boektrust.btsoep.shared.model.SupplyDetail;

import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import com.google.appengine.api.xmpp.JID;
import com.google.appengine.api.xmpp.Message;
import com.google.appengine.api.xmpp.MessageBuilder;
import com.google.appengine.api.xmpp.SendResponse;
import com.google.appengine.api.xmpp.XMPPService;
import com.google.appengine.api.xmpp.XMPPServiceFactory;

/**
 * @author rao3
 *
 * the implementation of the server that can serve XML requests to Koppelvlak bookstores. 
 */
public class KoppelvlakRPCServiceImpl extends HttpServlet {
	private boolean isObjectifyServiceRegistered = false;
	private ServletContext context;

	
	ServletContext getContext() {
		return this.context;
	}

	public void init() throws ServletException {
		context = getServletContext();
		//System.err.println("Real Path = " + context.getRealPath("/"));
	}

	/**
	 * 
	 */
	public KoppelvlakRPCServiceImpl() {
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param delegate
	 */
	public KoppelvlakRPCServiceImpl(Object delegate) {
		//super(delegate);
		// TODO Auto-generated constructor stub
	}
	
	
	
	public void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws ServletException, IOException  
	{
		
		/*
		 * get raw XML from the inputstream. 
		 */
		ServletInputStream inputStream = request.getInputStream();
		byte b[] = new byte[1024];
		int offset=0;
		try {
			offset = inputStream.read(b);
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		String rawxml = new String(b);
		int length = offset;

		rawxml = rawxml.substring(0, length-2);
		System.err.println("POST request received: " + rawxml);
		String responseString = new String();
		String methodName = new String();
		ArrayList<String> stringParameters = new ArrayList<String>();

		
		/*
		 * check if methodname is rpcHandler.search; if not, bork with an error and log it. 
		 */
		javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
		try {
			javax.xml.parsers.DocumentBuilder db = factory.newDocumentBuilder();
			InputSource inStream = new org.xml.sax.InputSource();

			inStream.setCharacterStream(new java.io.StringReader(rawxml));
			Document doc = db.parse(inStream);
			NodeList nl = doc.getDocumentElement().getChildNodes();
			//if (nl != null) System.err.println("Documentnode has "+nl.getLength()+" children");
			if (doc.getDocumentElement().getNodeName().equals("methodCall")) {
				for (int i=0; i<nl.getLength(); i++) {
					if (nl.item(i).getNodeName().equals("methodName")) {
						methodName = new String(nl.item(i).getFirstChild().getTextContent());
					} else if (nl.item(i).getNodeName().equals("params")) {
						//System.err.println("params found");
						NodeList paramsList = nl.item(i).getChildNodes();
						for (int a=0; a<paramsList.getLength(); a++ ) {
							if (paramsList.item(a).getNodeName().equals("param")) {
								NodeList paramNodeList = paramsList.item(a).getChildNodes();
								for (int j=0; j<paramNodeList.getLength(); j++) {
									if (paramNodeList.item(j).getNodeName().equals("value")) {
										//System.err.println("value found");

										NodeList valueNodeList = paramNodeList.item(j).getChildNodes();
										for (int k=0; k<valueNodeList.getLength(); k++) {
											if (valueNodeList.item(k).getNodeName().equals("string")) {
												if (valueNodeList.item(k).getFirstChild() != null && valueNodeList.item(k).getFirstChild().getTextContent() != null) {
												stringParameters.add(new String(valueNodeList.item(k).getFirstChild().getTextContent()));
												}


											}
										}
									}
						}
							}
						}
				}
				}
			} else {
				responseString = buildErrorResponse(0);
			}
			if (methodName.length() == 0) {
				responseString = buildErrorResponse(1);
			} else {
				if (!methodName.equals("rpcHandler.search") && !methodName.equals("rpcHandler.getBookDetails") && !methodName.equals("rpcHandler.readCB")) {
					responseString = buildErrorResponse(2);
				}
			}
			if (responseString.length() > 0) {
				//System.err.println("responseString is: "+responseString);
			} else {
				if (stringParameters.size() == 0) {
					//System.err.println("stringParameters size is 0");
				} else {
					//System.err.println("stringParameters:");
					
					/*
					 * parameter: 	interpretation:
					 * 1			key ('titel')
					 * 2			value ('tinkelbel')
					 * 3			leverbaarheid? ('J')
					 * 4			?
					 * 5			? (boolean, 0)
					 * 6			maxresults (int, 200)
					 */

					for (String st : stringParameters) {
						//System.err.println("parameter passed: "+st);
					}
					
					

					/*
					 * allocate a new doc and add the docframent that has the response
					 */
					factory = DocumentBuilderFactory.newInstance();
					db = factory.newDocumentBuilder();
					doc = db.newDocument();
					// only return valid results when we call rpcHandler.getBookDetails - for debugging. 
					if (methodName.equals("rpcHandler.search")) {
						/* 
						 * lookup the books
						 */
						List<PhysicalBookManifestation> books = null;
						if (stringParameters.get(0).equals("titel")) {
							System.err.println("Searching title / "+stringParameters.get(1));
							books = new PersistentServiceImpl().getBooksByTitle(stringParameters.get(1).toLowerCase());
							//sendMessage("Koppelvlak: gezocht op titel: \'"+stringParameters.get(1)+"\' resultaten: "+books.size());

						} else if (stringParameters.get(0).equals("isbn")) {
							books = new PersistentServiceImpl().getBooksByISBN(stringParameters.get(1));
							//sendMessage("Koppelvlak: gezocht op isbn: \'"+stringParameters.get(1)+"\' resultaten: "+books.size());


						} else if (stringParameters.get(0).equals("auteur")) {
							System.err.println("Searching auteur / "+stringParameters.get(1).toLowerCase());

							books = new PersistentServiceImpl().getBooksByAuthor(stringParameters.get(1));
							//sendMessage("Koppelvlak: gezocht op auteur: \'"+stringParameters.get(1)+"\' resultaten: "+books.size());


						} else {
							books = new ArrayList<PhysicalBookManifestation>();
							//books = new PersistentServiceImpl().getAllPhysicalBookManifestations();
						}
						System.err.println("Got " + books.size() + " books");
						doc.appendChild(doc.importNode(buildResponse(stringParameters, books, true, methodName), true)); 

					}  else if (methodName.equals("rpcHandler.getBookDetails")) {
						List<PhysicalBookManifestation> books = null;
							System.err.println("Searching rpcHandler.getBookDetails / "+stringParameters.get(0));
							//sendMessage("Koppelvlak: rpcHandler.getBookDetails: \'"+stringParameters.get(0));
							
							books = new PersistentServiceImpl().getBooksByRecordReference(stringParameters.get(0));
							System.err.println("Got " + books.size() + " books");
							doc.appendChild(doc.importNode(buildResponse(stringParameters, books, true, methodName), true)); 
					}  
					/*
					 * output the response
					 */
					response.setContentType("text/xml");
					Transformer transformer;
					System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
					transformer = TransformerFactory.newInstance().newTransformer();
					java.io.StringWriter writer = new StringWriter();
					javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(writer);
					DOMSource source = new DOMSource(doc);
					transformer.transform(source, result);
					String xmlString = result.getWriter().toString();
					//System.err.println("xmlString: " + xmlString);
					response.getOutputStream().print(xmlString);

					

				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}	
	} 
	
	 /**
	  * Fetch the entire contents of a text file, and return it in a String.
	  * This style of implementation does not throw Exceptions to the caller.
	  *
	  * @param aFile is a file which already exists and can be read.
	  */
	  static public String getContents(File aFile) {
	    //...checks on aFile are elided
	    StringBuilder contents = new StringBuilder();
	    
	    try {
	      //use buffering, reading one line at a time
	      //FileReader always assumes default encoding is OK!
	      BufferedReader input =  new BufferedReader(new FileReader(aFile));
	      try {
	        String line = null; //not declared within while loop
	        /*
	        * readLine is a bit quirky :
	        * it returns the content of a line MINUS the newline.
	        * it returns null only for the END of the stream.
	        * it returns an empty String if two newlines appear in a row.
	        */
	        while (( line = input.readLine()) != null){
	          contents.append(line);
	          contents.append(System.getProperty("line.separator"));
	        }
	      }
	      finally {
	        input.close();
	      }
	    }
	    catch (IOException ex){
	      ex.printStackTrace();
	    }
	    
	    return contents.toString();
	  }

	  
	  /**
		  * Fetch the entire contents of a text file, and return it in a String.
		  * This style of implementation does not throw Exceptions to the caller.
		  *
		  * @param aFile is a file which already exists and can be read.
		  */
		  static public String getContents2(File aFile) {
			  String contents = new String();
			  try {
			        FileInputStream fis =
			            new FileInputStream(aFile);
			        byte buf[] = new byte[20480];
			        int cnt = 0;
			        int n;
			        while ((n = fis.read(buf)) != -1) {
			        	/*
			          for (int i = 0; i < n; i++) {
			            if (buf[i] == '\n')
			              cnt++;
			          }
			          */
			          contents = contents + new String(buf);
			        }
			        fis.close();
			        System.out.println(cnt);
			      }
			      catch (IOException e) {
			        System.err.println(e);
			      }
		    return contents.toString();
		  }


		  /* 
		   * play function to send a message using XMPP 
		   */
		  void sendMessage(String st) {
			  if (st == null || st.length() == 0) return;

			  JID[] jids = new JID[5];
			  jids[0] = new JID("roelanto@gmail.com");
			  jids[1] = new JID("boektrust@gmail.com");
			  Message msg = new MessageBuilder()
			  .withRecipientJids(jids[0],jids[1])
			  .withBody(st)
			  .build();

			  boolean messageSent = false;
			  XMPPService xmpp = XMPPServiceFactory.getXMPPService();
			  SendResponse status = xmpp.sendMessage(msg);
			  messageSent = (status.getStatusMap().get(jids) == SendResponse.Status.SUCCESS);

			  if (!messageSent) {
				  System.err.println("Couldn't send message "+st);
				  // do nothing
			  }
		  }

	
	public static String stack2string(Exception e) {
		try {
			StringWriter sw = new StringWriter();
			PrintWriter pw = new PrintWriter(sw);
			e.printStackTrace(pw);
			return "------\r\n" + sw.toString() + "------\r\n";
		}
		catch(Exception e2) {
			return "bad stack2string";
		}
	}
	 

	public static org.w3c.dom.DocumentFragment getDocumentFragment() {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder db = factory.newDocumentBuilder();
			org.w3c.dom.Document doc = db.newDocument();
			org.w3c.dom.DocumentFragment documentFragment = doc.createDocumentFragment();
			return documentFragment;

		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
		
	}
	
	public DocumentFragment buildReadResponse (String s) {
		DocumentFragment documentFragment = KoppelvlakRPCServiceImpl.getDocumentFragment();
		Document doc = documentFragment.getOwnerDocument();
		Element e = documentFragment.getOwnerDocument().createElement("methodResponse");
		documentFragment.appendChild(e);
		Element e1 = documentFragment.getOwnerDocument().createElement("params");
		e.appendChild(e1);
		Element e2 = documentFragment.getOwnerDocument().createElement("param");
		e1.appendChild(e2);
		Element e8 = documentFragment.getOwnerDocument().createElement("value");
		e2.appendChild(e8);
		e8.appendChild(documentFragment.getOwnerDocument().createTextNode(s));
		return documentFragment;
		
	}
	
	/* 
	 * pass true in returnResults to actually return results. If false, this method will return a valid but empty XML response.
	 */
	public DocumentFragment buildResponse (ArrayList<String> stringValues, List<PhysicalBookManifestation> books, boolean returnResults, String methodName) {
		DocumentFragment documentFragment = KoppelvlakRPCServiceImpl.getDocumentFragment();
		Document doc = documentFragment.getOwnerDocument();
		Element e = documentFragment.getOwnerDocument().createElement("methodResponse");
		documentFragment.appendChild(e);
		Element e1 = documentFragment.getOwnerDocument().createElement("params");
		e.appendChild(e1);
		Element e2 = documentFragment.getOwnerDocument().createElement("param");
		e1.appendChild(e2);
		Element e8 = documentFragment.getOwnerDocument().createElement("value");
		e2.appendChild(e8);
		if (methodName.equals("rpcHandler.search")) {
			Element e3 = documentFragment.getOwnerDocument().createElement("array");
			if (returnResults == true ) e8.appendChild(e3);
			Element e4 = documentFragment.getOwnerDocument().createElement("data");
			e3.appendChild(e4);
			//String s = stringValues.get(0);
			for (PhysicalBookManifestation b : books) {
				Element e5 = documentFragment.getOwnerDocument().createElement("value");
				e5.appendChild(doc.importNode(buildBookNode(b, false), true));
				e4.appendChild(e5);

				//Element e6 = documentFragment.getOwnerDocument().createElement("string");
				//e6.appendChild(documentFragment.getOwnerDocument().createTextNode(s));
				//e5.appendChild(e6);
			}
		} else if (methodName.equals("rpcHandler.getBookDetails")) {
			e8.appendChild(doc.importNode(buildBookNode(books.get(0), true), true));
		}
		return documentFragment;
	}
	
	DocumentFragment buildBookNode(PhysicalBookManifestation bm, boolean detailed) {
		DocumentFragment docFragment = KoppelvlakRPCServiceImpl.getDocumentFragment();
		Element e = docFragment.getOwnerDocument().createElement("struct");
		Document doc = docFragment.getOwnerDocument();
		e.appendChild(doc.importNode(buildStringKVPair("bookid", new Long(bm.getRecordReference()).toString()), true));
		e.appendChild(doc.importNode(buildStringKVPair("title", bm.getTitle()), true));
		String authorString = new String("");
		for (Contributor c : bm.getContributors()) {
			if (c.getRoleCode() == ContributorRoleCode.AUTHOR) {
				if (authorString.length() == 0) {
					authorString = c.getNameOnBook();
				} else {
					authorString = authorString + ", " + c.getNameOnBook();
				}
			}
			
		}
		e.appendChild(doc.importNode(buildStringKVPair("author", authorString), true));
		//DateFormat formatter ;
		String year;
		if (bm.getPublicationDate() != null) {
			Calendar c = Calendar.getInstance();
			c.setTime(bm.getPublicationDate());
			year = new Integer(c.get(Calendar.YEAR)).toString();
		} else {
			year = "";
		}
		e.appendChild(doc.importNode(buildStringKVPair("yearofdistribution", year), true));
		e.appendChild(doc.importNode(buildStringKVPair("media", ProductFormCode.stringValue(bm.getProductFormCode())), true));
		if (bm.getSubtitle() != null && bm.getSubtitle().length() > 0) {
			e.appendChild(doc.importNode(buildStringKVPair("subtitle", bm.getSubtitle()), true));
		}
		if (bm.getProductFormCode() == ProductFormCode.PAPERBACK) {
			e.appendChild(doc.importNode(buildStringKVPair("covertype", "Gebonden"), true));
		} else {
			e.appendChild(doc.importNode(buildStringKVPair("covertype", ""), true));
		}
		e.appendChild(doc.importNode(buildStringKVPair("isbn", bm.getIsbn().getIsbn13()), true));
		e.appendChild(doc.importNode(buildStringKVPair("ean", bm.getIsbn().getIsbn13()), true));
		/*
		 * determine nur 
		 */
		String nurCode = new String("");
		for (ContentSpecifier c : bm.getContentSpecifiers()) {
			if (c.getContentSpecifierType() == ContentSpecifierType.NURCODE) {
				if (nurCode.length() == 0) nurCode = new Integer(c.getCode()).toString();
			}
		}
		e.appendChild(doc.importNode(buildStringKVPair("nur", nurCode), true));
		/*
		 * determine whether the book is available or not
		 */
		String availability = new String("N");
		System.err.println("dump of book: "+bm.toPrintString());
		float prijs = -1;
		for (SupplyDetail s : bm.getSupplyDetails()) {
			System.err.println("RecRef: "+bm.getRecordReference()+" availability: "+AvailabilityCode.stringValue(s.getAvailabilityCode()));
			if (s.getAvailabilityCode() == AvailabilityCode.AVAILABLE) {
				availability = new String("J");
			}
			int lowestPrice = 99999999;
			for (Price p: s.getPriceArray()) {
				if (p.getPriceAmountInCents() < lowestPrice && p.getPriceAmountInCents() > 0) {
					System.err.println("Found prijs: " + prijs);

					prijs = (float)p.getPriceAmountInCents() / 100;
				}
			}
		}
		e.appendChild(doc.importNode(buildStringKVPair("availability", availability), true));
		/*
		 * add a price child. Seems that the short koppelvlak version expects 'priceuro' and the detailed one 'price'. 
		 */
		if (prijs > -1) {
			DecimalFormat prijsFormat = new DecimalFormat("######.00");

			if (detailed == true ) {
				e.appendChild(doc.importNode(buildStringKVPair("price", prijsFormat.format(prijs)), true));
			} else {
				e.appendChild(doc.importNode(buildStringKVPair("priceeuro", prijsFormat.format(prijs)), true));
				e.appendChild(doc.importNode(buildStringKVPair("price", prijsFormat.format(prijs)), true));

			}
		}
		if (detailed == true) {
			for (ImageReference imageReference : bm.getImageReferences()) {
				if (imageReference.getImageReferenceTypeCode() == ImageReferenceTypeCode.FRONTCOVER) {
					if (imageReference.getUrl() != null) {
						e.appendChild(doc.importNode(buildStringKVPair("photo", imageReference.getUrl()), true));
					}
					
				}
			}
			e.appendChild(doc.importNode(buildStringKVPair("languagecodeThisDistribution", bm.getLanguageCode().toString()), true));


		}
		e.appendChild(doc.importNode(buildStringKVPair("more_editions", ""), true));
		docFragment.appendChild(e);
		return docFragment;
	}
	
	DocumentFragment buildStringKVPair(String key, String value) {
		DocumentFragment docFragment = KoppelvlakRPCServiceImpl.getDocumentFragment();
		Element e1 = docFragment.getOwnerDocument().createElement("member");
		Element e2 = docFragment.getOwnerDocument().createElement("name");
		Element e3 = docFragment.getOwnerDocument().createElement("value");
		Element e4 = docFragment.getOwnerDocument().createElement("string");
		e2.appendChild(docFragment.getOwnerDocument().createTextNode(key));
		e4.appendChild(docFragment.getOwnerDocument().createTextNode(value));
		e1.appendChild(e2);
		e1.appendChild(e3);
		e3.appendChild(e4);
		docFragment.appendChild(e1);

		return docFragment;
	}
	
	public String buildErrorResponse (int code) {
		String errorString = new String();;
		switch (code) {
		case 0:
			errorString = "the 1st node in the received request XML is not methodCall";
			break;
		case 1:
			errorString = "did not find a methodName node in XML list";
			break;
		case 2:
			errorString = "the methodName value in the received request XML is not rpcHandler.search";
			break;
		}
		return errorString;
	}
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
	throws ServletException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		Document doc = null;
		try {
			doc = factory.newDocumentBuilder().newDocument();
		} catch (Exception e) {
			System.err.println("Exception");
			e.printStackTrace();

			System.err.println(e.getMessage());
			System.err.println(e.getStackTrace());
			return;
		}
		 
		PrintWriter out = response.getWriter();
		response.setContentType("text/xml");
		if (request.getPathInfo() == null || request.getPathInfo().length()<13) {
			out.println("Error: no ISBN specified; usage: http://.../book/978906262331");
			return;
		}
		String isbnString = request.getPathInfo().substring(1);
		System.err.println("RestBook called with ISBN "+isbnString);

		isbnString = isbnString.substring(0, 12);
		System.err.println("Matching on substring "+isbnString);

		List<PhysicalBookManifestation> books = new PersistentServiceImpl().getAllPhysicalBookManifestations();
		System.err.println("Got " + books.size() + " books");
		PhysicalBookManifestation theBook = null;
		for (PhysicalBookManifestation book : books) {
			if (book.getIsbn().getIsbn12().equals(isbnString)) {
				theBook = book;
			}
		}
		if (theBook == null) {
			System.err.println("no books found");
			return;
		}
		System.err.println("First book: " +theBook.toPrintString());

		// encode the message
		DocumentFragment onixMessage = OnixUtility.getOnixMessage();
		if (onixMessage == null) {
			System.err.println("fragment is null");
			return;
		}
		doc.appendChild(doc.importNode(onixMessage, true));
		if (onixMessage.getOwnerDocument() == null) {
			System.err.println("owner document is null");
			return;
		}
		
		// add header
		DocumentFragment header = OnixUtility.getOnixMessageHeader();
		doc.getDocumentElement().appendChild(doc.importNode(header, true));
		
		// add product node for first product
		DocumentFragment product = OnixUtility.encodeToOnix(theBook);
		doc.getDocumentElement().appendChild(doc.importNode(product, true));
		/*
		System.out.println("Built an XML docFragment with "+onixMessage.getChildNodes().getLength()+" nodes");
		System.out.println("Nodes: ");
		for (int i = 0; i<onixMessage.getChildNodes().getLength(); i++) {
			System.out.println("node "+i+": "+onixMessage.getChildNodes().item(i).getNodeName());
			System.out.println("      has : "+onixMessage.getChildNodes().item(i).getChildNodes().getLength()+" children");
		}
		*/

		// transform and output the book
		Transformer transformer;
		try {
			// see: http://comments.gmane.org/gmane.org.google.gwt/45531
			System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
			transformer = TransformerFactory.newInstance().newTransformer();
			//transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			//javax.xml.transform.Source src = new DOMSource(docFragment.getOwnerDocument());
			//if (src == null) out.println("src is null");
			java.io.StringWriter writer = new StringWriter();
			javax.xml.transform.stream.StreamResult result = new javax.xml.transform.stream.StreamResult(writer);
			DOMSource source = new DOMSource(doc);
			//out.println("Got an XML; root node has  "+doc.getDocumentElement().getChildNodes().getLength()+" nodes");
			//out.println("First node under rootnode: "+doc.getDocumentElement().getChildNodes().item(0).getNodeName());
			transformer.transform(source, result);
			String xmlString = result.getWriter().toString();
			if (xmlString == null) out.println("xmlString is null");
			System.err.println("xmlString: " + xmlString);
			out.println("" + xmlString);
		} catch (Exception e) {
			System.err.println("Exception thrown: " + e.getMessage());
			out.print("Exception thrown: " + e.getMessage());
			e.printStackTrace();
		}

		
		
		//out.println("Querystring:     "+queryString);
		//out.println("RemoteAddress:   "+remoteAddress);
	}
	
	public static final void prettyPrint(Node xml, PrintWriter out)
	throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
		Transformer tf = TransformerFactory.newInstance().newTransformer();
		tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
		tf.setOutputProperty(OutputKeys.INDENT, "yes");
		tf.transform(new DOMSource(xml), new StreamResult(out));
	}

}
